/**
 * @author W
 * @date 2020/4/7 21:02
 */

// 条件类型
// 主要是使用了 extends 和 三元操作符
// 如: T extends U ? X : Y  意思是T是否是继承U类型，也就是T上有且仅拥有U的上的属性和方法，如果有返回新类型X，否则返回类型Y

// 判断 T 是否继承了 number类型，是就返回该类型，否则继续判断,最终返回Object类型
type TypeName<T> =
    T extends number ? number :
        T extends string ? string :
            T extends boolean ? boolean :
                T extends undefined ? undefined :
                    T extends null ? null :
                        T extends Function ? Function :
                            T extends symbol ? symbol : object;
{
    type A = TypeName<string>
    type B = TypeName<boolean>
    type C = TypeName<symbol>
    type D = TypeName<Function>
    type D1 = TypeName<(() => {})>
    type F = TypeName<undefined>; // strictNullChecks 关闭时 undefined 可以赋值给任何类型，也就是任何类型 extends undefined，开启时就不能
    type G = TypeName<string[]> // object

    // let a:A = 100  // error A类型为string，不能被number类型赋值
    let obj: G = { // G是object类型
        name: 'wm'
    }

    let arr: G = [] // G 是object类型，而数组在js中也是对象
}

// 如果是T类型是一种联合类型：(A | B)
// 如果 (A | B) extends U ? X : Y 会转换为： A extends U ? X : Y |  B extends U ? X : Y 结果也是联合类型如：X | Y
{
    type A = TypeName<number | string> // 会转换为 TypeName<number> | TypeName<string>，因此就是 A就是 number | string 的联合类型
    let a:A = 1  // number
    a = 'wang.mao' // string
    type B = TypeName<any[] | (()=>void)> // object | Function 的联合类型
    let b:B = [] // object
    b = function () {} // function
}

// 利用以上的特性就可以帮助我们实现一些类型上的过滤
// 如:从T中剔除可以赋值给U的类型,返回不可赋值给U的类型
{
    type Exclude<T,U> = T extends U ? never : T
    type num = Exclude<string|number|boolean, string|boolean> // 最终是number类型，因为T中的string和boolean 都可以赋值给U中的string和boolean
    // 像这种两种类型都是联合类型的会转换成多个条件类型的联合类型：
    // Exclude<string, string|boolean>  |  Exclude<number, string|boolean>  |  Exclude<boolean, string|boolean>
    // string extends string|boolean ? X : Y   |  number extends string|boolean ? X : Y   |  boolean extends string|boolean ? X : Y
    // string 类型可以继承 string，返回never类型， number 类型不能继承 boolean 和 string，返回number，boolean 类型能继承 boolean，返回never，
    // 结果是 never | number | never 的联合类型  => number 类型

    // 因为null类型可以赋值给null类型、undefined类型可以赋值undefined类型，结合上面的例子我们就可以剔除一个类型中的undefined、null类型,
    type NotNull<T> = Exclude<T, null | undefined>
    type A = NotNull<'a' | string | number | '' | null> // 'a' | string | number 的联合类型，因为 'a' 也是string 类型。所以最终是 string | number
    let a:A = 'a';
    a = 'str'
    a = 100
     // a = [] // error a的联合；类型不包括数组

    // 定义一个与Exclude相反的类型，也就是从T中抽取可以赋值给U的类型并返回
    type Extract<T,U> = T extends  U ? T : never;
    type B = Extract<'a'|'str'|'c','str'|'e' | 'a'>  // 返回 'str' 字符串字面量类型
    let b:B = 'str'
    b = 'a'
    // b = 'wang'  // b只能赋值'str'
}

//官方内置的 ReturnType<T>  返回一个函数的返回值的类型
{
    type A = ReturnType<()=>string> //string，因为函数的返回值是string
    let str:A = 'wang'
    // str = []  // error str的类型为string
}

// Omit<T,K>
{
    interface T {
        name:string,
        age:number
        class:symbol
    }

    // 获取T类型中属性'name' 和 'age' 的类型
    type B = Pick<T,'name'|'age'>
    let obj:B = {
        name:'wm',
        age:22
    }

    // Omit类型：获取T类型属性不在'a'|'b'|'c'|'age'|'class'中的类型
    // type C = Omit<T,'a'|'b'|'c'|'age'|'class'> // {name:string}

    // 上面的Omit类型其实由Exclude、Pick两个条件类型相结合而成
    // 1、先剔除T类型中能赋给'a'|'b'|'c'|'age'|'class'的类型，保留不能赋给的类型并返回，这里是name
    type D = Exclude<keyof T, 'a'|'b'|'c'|'age'|'class'> // name

    // 2、使用Pick 条件类型，获取T类型中属性D的类型，name的类型是string
    type E = Pick<T, D>  // {name:string}

    // 所以Omit 就是 Exclude 和 Pick 的结合体，咋一看很复杂，拆开看才能变得简单
    // type Omit<T,U extends keyof T> = Pick<T, Exclude<keyof T,U>>

    type F = Omit<{a:1,b:'2'}, 'a'> // 返回 {b:'2'}

    let f:F = {
        b:"2"
    }
    // f = '2'  // 报错
}
